﻿using System.Collections.Generic;
using System.Text;
using SinBiota.Model.Business.CoordinatesServices.Interfaces;
using SinBiota.Model.Business.MapServices.Datatypes;

namespace SinBiota.Model.Business.CoordinatesServices.Implementation
{
    public class CoordinatesConversion : ICoordinatesConversion
    {
        private const int MinASCII = 63;
        private const int BinaryChunkSize = 5;

        public float ConvertFromDegreesToDecimal(int degrees, int minutes, int seconds)
        {
            return (degrees + (float) minutes / 60 + (float) seconds / 3600);
        }


        /// <summary>
        /// encoded a list of latlon objects into a string
        /// </summary>
        /// <param name="points">the list of latlon objects to encode</param>
        /// <returns>the encoded string</returns>
        public string EncodeLatLong(List<LatLong> points)
        {
            int plat = 0;
            int plng = 0;
            int len = points.Count;

            var encodedPoints = new StringBuilder();

            for (int i = 0; i < len; ++i)
            {
                //Round to 5 decimal places and drop the decimal
                int late5 = (int)(points[i].Latitude * 1e5);
                int lnge5 = (int)(points[i].Longitude * 1e5);

                //encode the differences between the points
                encodedPoints.Append(EncodeSignedNumber(late5 - plat));
                encodedPoints.Append(EncodeSignedNumber(lnge5 - plng));

                //store the current point
                plat = late5;
                plng = lnge5;
            }
            return encodedPoints.ToString();
        }

        /// <summary>
        /// Encode a signed number in the encode format.
        /// </summary>
        /// <param name="num">the signed number</param>
        /// <returns>the encoded string</returns>
        private static string EncodeSignedNumber(int num)
        {
            int sgn_num = num << 1; //shift the binary value
            if (num < 0) //if negative invert
            {
                sgn_num = ~(sgn_num);
            }
            return (EncodeNumber(sgn_num));
        }

        /// <summary>
        /// Encode an unsigned number in the encode format.
        /// </summary>
        /// <param name="num">the unsigned number</param>
        /// <returns>the encoded string</returns>
        private static string EncodeNumber(int num)
        {
            var encodeString = new StringBuilder();
            while (num >= 0x20)
            {
                //while another chunk follows
                encodeString.Append((char)((0x20 | (num & 0x1f)) + MinASCII));
                //OR value with 0x20, convert to decimal and add 63
                num >>= BinaryChunkSize; //shift to next chunk
            }
            encodeString.Append((char)(num + MinASCII));
            return encodeString.ToString();
        }
    }
}